package mya_dc.master_server.threads;

import java.awt.TrayIcon;
import java.io.IOException;
import java.net.Socket;

import mya_dc.master_server.MasterServerGUI;
import mya_dc.master_server.database.ClientsHashTable;
import mya_dc.master_server.database.ServersHashTable;
import mya_dc.shared_classes.ConnectInfo;
import mya_dc.shared_classes.MYA_Authentication;
import mya_dc.shared_classes.Message;
import mya_dc.shared_classes.SharedDefinitions;
import mya_dc.shared_classes.Transmitter;
import mya_dc.shared_classes.Message.EMessageType;


/**
 * Handles the registrations of a new client on the master server.
 *
 * @author      Adam Levi
 * @author		Yuval Shimron
 * <br>			MYA
 */
public class ClientListenerThread extends MYA_ConnectionThread implements Runnable
{
	/**
	 * Constructor
	 * 
	 * @param ParentGUI - The parent's GUI
	 * @param clientsArray - The client's Hash-Table
	 * @param compilationServers - The Server's Hash-Table
	 */
	public ClientListenerThread(
			MasterServerGUI ParentGUI,
			ClientsHashTable clientsArray,
			ServersHashTable compilationServers,
			Socket clientSocket
			) throws IOException
	{
		super(
				new Transmitter(
						clientSocket,
						null,
						SharedDefinitions.SecsBetweenTwoKeepAliveMessages));
		m_ParentGUI = ParentGUI;
		m_ContinueFlag = true;
		m_ClientsArray = clientsArray;
		m_CompilationServers = compilationServers;
		m_UserProjectDetails = null;
	}
	
	/**
	 * runs the thread
	 */
	@Override
	public void run()
	{
		try
		{
			while (true) 
			{
				if (!SharedDefinitions.isExpectedEntity(
						m_Transmitter,
						SharedDefinitions.Entity.ENTITY_CLIENT,
						true)
					)
				{
					return;
				}
				Message tmpMessage = m_Transmitter.receive();
				
				if (tmpMessage.getType() != Message.EMessageType.LOGIN)
				{
					//Unknown Message 
					return;
				}
				
				m_UserProjectDetails = (MYA_Authentication)tmpMessage.getObject();
				
				if (!doAuthenticate())
				{
					m_Transmitter.send(new Message(EMessageType.LOGIN_FAILED_AUTHENTICATION_FAILED,null));
					return;
				}
				if (isMappingToServerExists())
				{
					if (doLoginExistingClient() == -1)
						continue;
					return;
				}
				else
				{
					doLoginNewClient();
					return;
				}
			}
		}
		catch (Exception e)
		{
			return;
		}
		finally
		{

			if (m_UserProjectDetails != null)
			{
				m_ParentGUI.UpdateClientsTables();
				m_ParentGUI.SendNotification("Information", "Accepted connection from Client " + m_UserProjectDetails.getUserName(),
						TrayIcon.MessageType.INFO);
			}
			m_Transmitter.close();
		}
	}
	
	/**
	 * starts the login process for an existing client
	 */
	private int doLoginExistingClient() throws IOException, ClassNotFoundException
	{
		Long serverID = m_ClientsArray.getServer(m_UserProjectDetails);
		ConnectInfo serverConnectInfo = m_CompilationServers.getServer(serverID);
		Message tmpMsg = new Message((serverConnectInfo == null ?
				EMessageType.LOGIN_FAILED_SERVER_DISCONNECTED : 
				EMessageType.LOGIN_COMPILATION_SERVER_INFO),serverConnectInfo);
		m_Transmitter.send(tmpMsg);
		switch(m_Transmitter.receive().getType())
		{
			case LOGIN_DELETE_MAPPING:
			{
				// mapping is wrong
				removeMapping();
				return -1; // while loop will get login message again
			}	
			case SUCCESS:
			default:
			{
				// Nothing to do
				m_Transmitter.close();
				return 0;
			}
		}
	}
	
	/**
	 * starts the login process for a new client
	 */
	private void doLoginNewClient() throws IOException, ClassNotFoundException
	{
		Object[] serversInfoArr = m_CompilationServers.getConnectedServers();
		Message msg = new Message(
				EMessageType.LOGIN_COMP_SERVER_LIST_OF_SERVERS,serversInfoArr);
		m_Transmitter.send(msg);
		msg = m_Transmitter.receive();
		switch(msg.getType())
		{
			case SUCCESS:
			{
				Long serverID = (Long)msg.getObject();
				m_ClientsArray.add(m_UserProjectDetails, serverID);
				m_CompilationServers.registerClient(serverID);
				return;
			}
			default:
			{
				m_Transmitter.close();
				return;
			}
		}
	}
	
	/**
	 * checks if the current client has a mapping to a compilation server
	 * 
	 * @return true is the user and project for the current user is mapped
	 * to a server, false otherwise
	 */
	private boolean isMappingToServerExists()
	{
		if (m_ClientsArray.getServer(m_UserProjectDetails)==null)
			return false;
		return true;
	}
	
	/**
	 * removes the given userProject mapping from the hash-table
	 */
	private void removeMapping()
	{
		m_ClientsArray.remove(m_UserProjectDetails);
	}

	/**
	 * checks if the user exists in the DB, if not it will add the user
	 * and return true, otherwise it will will check if the password in
	 * the DB is equal to the user's password and return the result of
	 * that check. 
	 * 
	 * @return true if the user's password is equal to to the password
	 * in the system or if the user doesn't appear in the system, false
	 * otherwise 
	 */
	private boolean doAuthenticate()
	{
		if (m_ClientsArray.isPasswordOK(
				m_UserProjectDetails.getUserName(),
				m_UserProjectDetails.getPassword()))
		{
			m_ClientsArray.addUserToUsersList(
					m_UserProjectDetails.getUserName(),
					m_UserProjectDetails.getPassword());
			return true;
		}
		return false;
	}


	//variables
	private MasterServerGUI 			m_ParentGUI;
	private ClientsHashTable			m_ClientsArray;
	private ServersHashTable			m_CompilationServers;
	private MYA_Authentication			m_UserProjectDetails;
	boolean m_ContinueFlag;
	
}
